
package models.spider.parser.aaa.json;

import java.util.ArrayList;
import java.util.List;

import org.apache.commons.lang3.StringUtils;

import models.aaa.Asset;
import models.aaa.Identifier;
import models.aaa.Tag;
import models.aaa.Url;
import models.spider.ISpider;
import models.spider.parser.IParser;
import models.spider.request.Factory;
import models.spider.request.Request;

/**
 * 
 * @author laurent@opprecht.info
 */
public class Parser implements IParser {
	
	private Factory requestFactory = null;

    public Parser() {
        this.requestFactory=new models.spider.request.Factory();
    }

    /**
     * Returns the resources from the url
     * 
     * @param url
     * @return
     */
    public List<Resource> getResources(final String url) {
        Request request = requestFactory.create(url);
        return getResources(request);
    }

    /**
     * Returns the resources from the Request
     * 
     * @param request
     * @return
     */
    public List<Resource> getResources(final Request request) {
        List<Resource> result = new ArrayList<>();
        if (!request.isValid()) {
            return result;
        }

        String text = request.body().asText().trim();
        boolean isJson = text.substring(0, 1).equals("{");
        if (!isJson) {
            return result;
        }

        Document doc = new Document(request);
        result = doc.resources;
        for (Resource item : result) {
            if (item.data == null || item.data.equals("")) {
                item.data = request.getUrl();
            }
        }
        return doc.resources;
    }

    /**
     * Returns the Assets from the url (with the exception of links)
     * 
     * @param url
     * @return
     */
    public List<Asset> getAssets(final String url) {
        List<Asset> result = new ArrayList<>();

        Asset a = Asset.find.byUrl(url);
        if (a != null) {
            result.add(a);
            return result;
        }

        List<Resource> items = getResources(url);
        for (Resource item : items) {
            Asset asset = resourceToAssetShallow(item);
            if (asset != null) {
                result.add(asset);
            }
        }
        return result;
    }

    /**
     * Parse the request. Returns the Assets contained in the resource
     * (including links). On failure mark the web resource as being inactive.
     * 
     */
    @Override
    public List<Asset> parse(final Request request, final ISpider spider) {
        List<Asset> result = new ArrayList<>();
        if (!request.isValid()) {
            Asset asset = onFailure(request);
            if (asset != null) {
                result.add(asset);
            }
            return result;
        }

        String text = request.body().asText().trim();
        boolean isJson = text.substring(0, 1).equals("{");
        if (!isJson) {
            return result;
        }

        Document doc = new Document(request);
        for (Resource resource : doc.resources) {
            Asset about = null;

            if (StringUtils.isEmpty(resource.data)) {
                resource.data = request.getUrl();
            }

            /**
             * Try to improve parallelism by adding the link urls as soon as
             * they are available. This is mostly interesting for resources
             * having a lot of links - i.e. the root sites like Moodle, etc.
             * 
             * Note that several urls can map to the same asset - for example to
             * the same resource. The consequence is that we need to fetch the
             * linked resource in order to identify it on the first run. On
             * subsequent run the url will be present and will map to the asset.
             * 
             * For this scheme to work spider.add should run in its own
             * transaction. That is with the current scheme in its own thread.
             * Which is going to be the case with the actor system.
             */
            for (Link l : resource.actors) {
                spider.add(doc.base+l.sid);
            }
            for (Link l : resource.assets) {
                spider.add(doc.base+l.sid);
            }
            for (Link l : resource.spaces) {
                spider.add(doc.base+l.sid);
            }
            // @todo: re-add the try catch but not the exception
            // try {
            // JPA.em().getTransaction().begin();
            about = resourceToAsset(resource);
            // JPA.em().getTransaction().commit();
            // } catch (Exception ex) {
            // JPA.em().getTransaction().rollback();
            // Logger.error("Parsing Error url=" + request.getUrl().toString(),
            // ex);
            // about = onFailure(request);
            // }

            if (about != null) {
                result.add(about);
            }
        }

        return result;
    }

    /**
     * 
     * @param request
     * @return
     */
    public Asset onFailure(final Request request) {
        String url = request.getUrl();
        Url u = Url.find.byUrl(url);
        if (u == null) {
            return null;
        }
        models.aaa.Resource webResource = models.aaa.Resource.find.byUrl(u);
        if (webResource == null) {
            return null;
        }
        webResource.setValid(false);
        Asset result = webResource.getAsset();

        return result;
    }

    /**
     * Convert the resource to an asset but ignore links.
     * 
     * @param resource
     * @return
     */
    public Asset resourceToAssetShallow(final Resource resource) {
        resource.pid = (resource.pid == null) ? resource.sid : resource.pid;
        Identifier pid = Identifier.get("pid", resource.pid);
        if (pid == null) {
            return null;
        }

        Asset result = null;
        if (result == null) {
            result = pid.getAsset();
        }

        String url = resource.data;
        models.aaa.Resource webResource = models.aaa.Resource.get(url);
        if (result == null) {
            result = new Asset(webResource);
        }

        webResource.setAbout(resource.url);
        result.add(webResource);

        result.add(pid);

        List<Tag> tags = Tag.get(resource.tags);
        result.setTags(tags);

        if (resource.type.toLowerCase().indexOf("space") >= 0) {
            result.setType(Asset.Type.SPACE);
        } else if (resource.type.toLowerCase().indexOf("asset") >= 0) {
            result.setType(Asset.Type.ASSET);
        } else if (resource.type.toLowerCase().indexOf("actor") >= 0) {
            result.setType(Asset.Type.ACTOR);
        } else if (resource.type.toLowerCase().indexOf("activity") >= 0) {
            result.setType(Asset.Type.ACTIVITY);
        } else {
            result.setType(Asset.Type.EMPTY);
        }

        result.setLid(resource.sid);
        result.setUid(resource.pid);
        result.setDataset(resource.dataset);
        result.setName(resource.name);
        result.setDescription(resource.description);
        result.setValid(true);

        return result;
    }

    /**
     * Convert the resource to an asset and follow the firts level of links.
     * 
     * @param resource
     * @return
     */
    public Asset resourceToAsset(final Resource resource) {
        Asset result = resourceToAssetShallow(resource);
        if (result == null) {
            return null;
        }

        String base = resource.data.substring(0,resource.data.lastIndexOf('/')+1);
        result.getLinks().clear();
        for (Link l : resource.actors) {
            List<Asset> tos = getAssets(base+l.sid);
            result.getLinks().addAll(tos);
        }
        for (Link l : resource.assets) {
            List<Asset> tos = getAssets(base+l.sid);
            result.getLinks().addAll(tos);
        }
        for (Link l : resource.spaces) {
            List<Asset> tos = getAssets(base+l.sid);
            result.getLinks().addAll(tos);
        }

        return result;
    }

 }